﻿using IOP.SgrA;
using IOP.SgrA.GLSL;
using IOP.SgrA.SilkNet.Vulkan;
using Microsoft.Extensions.Logging;
using Silk.NET.Vulkan;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static System.Net.Mime.MediaTypeNames;

namespace VkSample1214
{
    [ModulePriority(ModulePriority.RenderGroup)]
    public class PipelineModule : VulkanModule, IVulkanRenderWapperModule
    {
        public VulkanRenderWapper RenderWapper { get; set; }

        public SecondaryVulkanRenderGroup CommonGroup {  get; set; }
        public SecondaryVulkanRenderGroup ParticleGroupOne { get; set; }
        public SecondaryVulkanRenderGroup ParticleGroupTwo { get; set; }
        public SecondaryVulkanRenderGroup ParticleGroupThree { get; set; }
        public SecondaryVulkanRenderGroup ParticleGroupFour { get; set; }

        private readonly ILogger<PipelineModule> _Logger;
        public PipelineModule(ILogger<PipelineModule> logger)
        {
            _Logger = logger;
        }

        protected override Task Load(VulkanGraphicsManager graphicsManager)
        {
            try
            {
                var lDevice = graphicsManager.VulkanDevice;
                var pipeCache = lDevice.PipelineCache;
                var disapatcher = RenderWapper.GetRenderDispatcher<VulkanSwapchainRenderDispatcher>();
                var presentPass = disapatcher.SwapchainRenderPass;
                var area = RenderWapper.RenderArea;
                var basePath = AppContext.BaseDirectory;
                var fence = lDevice.CreateFence();

                var camera = graphicsManager.CreateCamera("main", new Vector3(0, 2.0f, 18), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
                camera.EncircleForX(Vector3.Zero, -30 * MathF.PI / 180.0f);
                float aspcet = (float)area.Width / area.Height;
                camera.SetProjectMatrix(-aspcet, 1.0f, 1.5f, 1000, reversedZ: true);
                camera.SetViewport(0, 0, area.Width, area.Height, 0.0f, 1.0f);

                var pCommand = lDevice.CreateCommandPool((device, option) =>
                {
                    option.Flags = CommandPoolCreateFlags.ResetCommandBufferBit;
                    option.QueueFamilyIndex = device.WorkQueues[0].FamilyIndex;
                }).CreatePrimaryCommandBuffer();
                var mainGroup = graphicsManager.CreatePrimaryRenderGroup("Main", null) 
                    .Binding(pCommand, lDevice, presentPass, [disapatcher.ImageAvailableSemaphore, disapatcher.RenderFinishSemaphore], [fence.RawHandle]);
                mainGroup.CreateGroupRenderingAction((builder) => builder.Run(group => { PrimaryGroupMain(group); }));
                var commonPipe = graphicsManager.BuildScriptedShaderAndPipeline<CommonPipe>(graphicsManager.NewStringToken(), presentPass, pipeCache, area);
                var particleOne = graphicsManager.BuildScriptedShaderAndPipeline<ParticleOnePipe>(graphicsManager.NewStringToken(), presentPass, pipeCache, area);
                var dynamicBuffer = graphicsManager.CreateDynamicUniformBufferTexture(graphicsManager.NewStringToken(), (ulong)(Marshal.SizeOf<ParticleBuffer>() * 20), 0, 0, 1, 0);
                particleOne.AddPipelineTexture(0, 0, dynamicBuffer);

                var particleTwo = graphicsManager.BuildScriptedShaderAndPipeline<ParticleTwoPipe>(graphicsManager.NewStringToken(), presentPass, pipeCache, area);
                var dynamicBufferTwo = graphicsManager.CreateDynamicUniformBufferTexture(graphicsManager.NewStringToken(), (ulong)(Marshal.SizeOf<ParticleBuffer>() * 20), 0, 0, 1, 0);
                particleTwo.AddPipelineTexture(0, 0, dynamicBufferTwo);

                var particleThree = graphicsManager.BuildScriptedShaderAndPipeline<ParticleThreePipe>(graphicsManager.NewStringToken(), presentPass, pipeCache, area);
                var dynamicBufferThree = graphicsManager.CreateDynamicUniformBufferTexture(graphicsManager.NewStringToken(), (ulong)(Marshal.SizeOf<ParticleBuffer>() * 20), 0, 0, 1, 0);
                particleThree.AddPipelineTexture(0, 0, dynamicBufferThree);

                var particleFour = graphicsManager.BuildScriptedShaderAndPipeline<ParticleFourPipe>(graphicsManager.NewStringToken(), presentPass, pipeCache, area);
                var dynamicBufferFour = graphicsManager.CreateDynamicUniformBufferTexture(graphicsManager.NewStringToken(), (ulong)(Marshal.SizeOf<ParticleBuffer>() * 20), 0, 0, 1, 0);
                particleFour.AddPipelineTexture(0, 0, dynamicBufferFour);

                var commonC = lDevice.CreateCommandPool((device, option) =>
                {
                    option.Flags = CommandPoolCreateFlags.ResetCommandBufferBit;
                    option.QueueFamilyIndex = device.WorkQueues[0].FamilyIndex;
                }).CreateSecondaryCommandBuffer();
                var commonG = graphicsManager.CreateSecondaryVulkanRenderGroup("Common", commonPipe).Binding(mainGroup, commonC, [], []);
                commonG.CreateGroupRenderingAction((builder) => builder.Run((group) => { CommonMain(group); }));
                commonG.SetCamera(camera);
                CommonGroup = commonG;

                var sGroupOne = graphicsManager.CreateSecondaryVulkanRenderGroup("ParticleOne", particleOne).Binding(CommonGroup);
                sGroupOne.SetCamera(camera);
                sGroupOne.CreateGroupRenderingAction((builder) => builder.Run((group) => { ParticleMain(group); }));
                ParticleGroupOne = sGroupOne;

                var sGroupTwo = graphicsManager.CreateSecondaryVulkanRenderGroup("ParticleTwo", particleTwo).Binding(CommonGroup);
                sGroupTwo.SetCamera(camera);
                sGroupTwo.CreateGroupRenderingAction((builder) => builder.Run((group) => { ParticleMain(group); }));
                ParticleGroupTwo = sGroupTwo;

                var sGroupThree = graphicsManager.CreateSecondaryVulkanRenderGroup("ParticleThree", particleThree).Binding(CommonGroup);
                sGroupThree.SetCamera(camera);
                sGroupThree.CreateGroupRenderingAction((builder) => builder.Run((group) => { ParticleMain(group); }));
                ParticleGroupThree = sGroupThree;

                var sGroupFour = graphicsManager.CreateSecondaryVulkanRenderGroup("ParticleFour", particleFour).Binding(CommonGroup);
                sGroupFour.SetCamera(camera);
                sGroupFour.CreateGroupRenderingAction((builder) => builder.Run((group) => { ParticleMain(group); }));
                ParticleGroupFour = sGroupFour;

                RenderWapper.CreateAndCutScene<Scene>("Main", mainGroup, typeof(InputSystem));
            }
            catch (Exception e)
            {
                _Logger.LogError(e, "");
            }
            return Task.CompletedTask;
        }

        protected override Task Unload(VulkanGraphicsManager manager)
        {
            return Task.CompletedTask;
        }

        private void PrimaryGroupMain(PrimaryVulkanRenderGroup group)
        {
            try
            {
                var queue = group.WorkQueues[0].Queue;
                var cmdBuffer = group.PrimaryCommandBuffer;
                var semaphores = group.Semaphores;
                var pass = group.RenderPass;
                var face = group.Fences;
                var lDevice = group.LogicDevice;

                VulkanFrameBuffer framebuffer = group.RenderPass.GetFramebuffer(group.CurrentFrame);
                cmdBuffer.Reset();
                cmdBuffer.Begin();
                cmdBuffer.BeginRenderPass(pass, framebuffer, pass.BeginOption, SubpassContents.SecondaryCommandBuffers);
                group.SecondaryGroupRendering(pass, framebuffer);
                cmdBuffer.ExecuteCommands(group.GetUpdateSecondaryBuffers());
                cmdBuffer.EndRenderPass();
                cmdBuffer.End();

                var submitInfo = new SubmitOption
                {
                    WaitDstStageMask = [PipelineStageFlags.ColorAttachmentOutputBit],
                    WaitSemaphores = [semaphores[0]],
                    Buffers = [cmdBuffer.RawHandle],
                    SignalSemaphores = [semaphores[1]]
                };
                lDevice.Submit(queue, face[0], submitInfo);
                Result r = Result.Timeout;
                do
                {
                    r = group.LogicDevice.WaitForFences(face, true, 100000000);
                } while (r == Result.Timeout);
                group.LogicDevice.ResetFences(face);
            }
            catch (Exception e)
            {
                group.Logger.LogError(e.Message + "\r\n" + e.StackTrace);
                group.Disable();
            }
        }

        private void CommonMain(SecondaryVulkanRenderGroup group)
        {
            var camera = group.Camera;
            var view = camera.ViewMatrix;
            var project = camera.ProjectionMatrix;
            var cmd = group.SecondaryCommandBuffer;
            var pipeline = group.Pipeline;

            CommandBufferInheritanceInfo info = new CommandBufferInheritanceInfo
            {
                Framebuffer = group.Framebuffer.RawHandle,
                OcclusionQueryEnable = false,
                RenderPass = group.RenderPass.RawHandle,
                Subpass = 0
            };
            cmd.Reset();
            cmd.Begin(CommandBufferUsageFlags.OneTimeSubmitBit |
                CommandBufferUsageFlags.RenderPassContinueBit, info);
            cmd.BindPipeline(PipelineBindPoint.Graphics, group.Pipeline);

            var viewPort = camera.Viewport;
            cmd.SetViewport(new Viewport(viewPort.X, viewPort.Y, viewPort.Width, viewPort.Height, viewPort.MinDepth, viewPort.MaxDepth));
            cmd.SetScissor(new Rect2D(null, new Extent2D((uint)viewPort.Width, (uint)viewPort.Height)));

            int size = 64;
            byte[] data = ArrayPool<byte>.Shared.Rent(size);
            Span<byte> span = data;
            span = span[..size];
            foreach (var item in group.GetContexts())
            {
                item.SetViewMatrix(in view);
                item.SetProjectionMatrix(in project);
                item.SetCamera(camera);
                var render = item.GetContextRenderObject();
                ref MVPMatrix local = ref item.GetMVPMatrix();
                Matrix4x4 mat = local.GetFinalMatrix();
                mat.ToBytes(ref span, 0);
                var tex = render.GetComponents<VulkanTexture>().First();
                var vros = render.GetComponents<VRO>();
                cmd.BindDescriptorSets(PipelineBindPoint.Graphics, pipeline.PipelineLayout, 0, null, [tex.DescriptorSet]);
                cmd.PushConstants(pipeline.PipelineLayout, ShaderStageFlags.VertexBit, 0, span);
                foreach(var vro in vros)
                {
                    cmd.BindVertexBuffers(0, [0], vro.VerticesBufferInfo.Buffer);
                    cmd.Draw(vro.VecticesCount, 1, 0, 0);
                }
            }

            foreach(var child in group.GetChildrens())
            {
                child.GroupRendering();
            }
            cmd.End();
            ArrayPool<byte>.Shared.Return(data);
        }

        private void ParticleMain(SecondaryVulkanRenderGroup group)
        {
            var camera = group.Camera;
            var view = camera.ViewMatrix;
            var project = camera.ProjectionMatrix;
            var cmd = group.SecondaryCommandBuffer;
            var pipeline = group.Pipeline;

            cmd.BindPipeline(PipelineBindPoint.Graphics, group.Pipeline);

            var viewPort = camera.Viewport;
            cmd.SetViewport(new Viewport(viewPort.X, viewPort.Y, viewPort.Width, viewPort.Height, viewPort.MinDepth, viewPort.MaxDepth));
            cmd.SetScissor(new Rect2D(null, new Extent2D((uint)viewPort.Width, (uint)viewPort.Height)));

            int size = 104;
            byte[] data = ArrayPool<byte>.Shared.Rent(size);
            Span<byte> span = data;
            span = span[..size];
            foreach (var item in group.GetContexts())
            {
                item.SetViewMatrix(in view);
                item.SetProjectionMatrix(in project);
                item.SetCamera(camera);
                var render = item.GetContextRenderObject();
                var particle = render.GetComponent<ParticleSystem>(nameof(ParticleSystem));

                ref MVPMatrix local = ref item.GetMVPMatrix();
                local.ModelMatrix =  Matrix4x4.CreateRotationY(particle.YAngle) * Matrix4x4.CreateTranslation(particle.PositionX, 3.5f, particle.PositionZ);
                Matrix4x4 mat = local.GetFinalMatrix();
                mat.ToBytes(ref span, 0);
                MemoryMarshal.Write(span.Slice(64, 16), particle.StartColor);
                MemoryMarshal.Write(span.Slice(80, 16), particle.EndColor);
                MemoryMarshal.Write(span.Slice(96, 4), 5.0f);
                MemoryMarshal.Write(span.Slice(100, 4), particle.Radis);

                var block = render.GetComponents<ITextureBlock>().First();
                block.UpdateBlockData(span);
                particle.Update(camera.EyePosition);
                var vro = render.GetComponents<VRO>().First();
                vro.UpdateMeshData(MemoryMarshal.AsBytes(new Span<float>(particle.Points)), Vector3.One, Vector3.One);
                var tex = render.GetComponents<VulkanTexture>().First(x => x.SetIndex == 1);

                cmd.BindDescriptorSets(PipelineBindPoint.Graphics, pipeline.PipelineLayout, 0, [block.BlockOffset], [pipeline.GetPipelineDescriptorSet(0), tex.DescriptorSet]);
                cmd.BindVertexBuffers(0, [0], vro.VerticesBufferInfo.Buffer);
                cmd.Draw(vro.VecticesCount, 1, 0, 0);
            }
            
            ArrayPool<byte>.Shared.Return(data);
        }
    }

    public struct ParticleBuffer
    {
        public Matrix4x4 MVP;
        public Vector4 StartColor;
        public Vector4 EndColor;
        public float MaxLifeSpan;
        public float BJ;
    }
}
